package cn.iocoder.yudao.module.product.property.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.product.property.dal.dataobject.ProductPropertyDO;
import cn.iocoder.yudao.module.product.property.dal.dataobject.ProductPropertyValueDO;
import cn.iocoder.yudao.module.product.property.dal.mysql.ProductPropertyMapper;
import cn.iocoder.yudao.module.product.property.dal.mysql.ProductPropertyValueMapper;
import cn.iocoder.yudao.module.product.property.vo.AppProductPropertySaveReqVO;
import cn.iocoder.yudao.module.product.property.vo.ProductPropertyPageReqVO;
import cn.iocoder.yudao.module.product.property.vo.ProductPropertySaveReqVO;
import cn.iocoder.yudao.module.product.sku.service.ProductSkuService;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserLastShopId;
import static cn.iocoder.yudao.module.api.product.ErrorCodeConstants.*;

/**
 * 产品 SKU项 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class ProductPropertyServiceImpl implements ProductPropertyService {

    @Resource
    private ProductPropertyMapper productPropertyMapper;
    @Resource
    private ProductPropertyValueMapper productPropertyValueMapper;

    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ProductPropertyValueService productPropertyValueService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private ProductSkuService productSkuService;

    private final Lock lock = new ReentrantLock();

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long createProperty(ProductPropertySaveReqVO createReqVO) {
        Long shopId = getLoginUserLastShopId();
        // 如果已经添加过该规格项，直接返回
        ProductPropertyDO dbProperty = productPropertyMapper.selectByShopIdAndName(shopId, createReqVO.getName());
        if (dbProperty != null) {
            return dbProperty.getId();
        }
        // 插入
        ProductPropertyDO property = BeanUtils.toBean(createReqVO, ProductPropertyDO.class);
        createReqVO.setId(null);
        property.setShopId(shopId);
        productPropertyMapper.insert(property);
        // 返回
        return property.getId();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long createProperty(AppProductPropertySaveReqVO createReqVO) {
        ProductPropertySaveReqVO reqVO = BeanUtils.toBean(createReqVO, ProductPropertySaveReqVO.class);
        return createProperty(reqVO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateProperty(ProductPropertySaveReqVO updateReqVO) {
        ProductPropertyDO propertyDO = validatePropertyExists(updateReqVO.getId());
        // 校验名字重复
        ProductPropertyDO property = productPropertyMapper.selectByShopIdAndName(propertyDO.getShopId(), updateReqVO.getName());
        if (property != null && ObjUtil.notEqual(property.getId(), updateReqVO.getId())) {
            throw exception(PROPERTY_EXISTS);
        }
        // 更新
        ProductPropertyDO updateObj = BeanUtils.toBean(updateReqVO, ProductPropertyDO.class);
        productPropertyMapper.updateById(updateObj);
        // 更新 sku 相关属性
        productSkuService.updateSkuProperty(updateObj.getId(), updateObj.getName());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateProperty(AppProductPropertySaveReqVO updateReqVO) {
        ProductPropertySaveReqVO reqVO = BeanUtils.toBean(updateReqVO, ProductPropertySaveReqVO.class);
        updateProperty(reqVO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deletePropertyById(Long id) {
        // 校验存在
        ProductPropertyDO propertyDO = validatePropertyExists(id);
        // 校验其下是否有规格值
        if (productPropertyValueService.getPropertyValueCountByPropertyId(propertyDO.getId()) > 0) {
            throw exception(PROPERTY_DELETE_FAIL_VALUE_EXISTS);
        }
        // 删除
        productPropertyMapper.deleteById(propertyDO.getId());
        // 同步删除规格值
        productPropertyValueService.deletePropertyValuePropertyId(propertyDO.getId());
    }

    private ProductPropertyDO validatePropertyExists(Long id) {
        ProductPropertyDO propertyDO = productPropertyMapper.selectById(id);
        if (propertyDO == null) {
            throw exception(PROPERTY_NOT_EXISTS);
        }
        return propertyDO;
    }

    @Override
    public PageResult<ProductPropertyDO> getPropertyPage(ProductPropertyPageReqVO pageReqVO) {
        return productPropertyMapper.selectPage(pageReqVO);
    }

    @Override
    public ProductPropertyDO getProperty(Long id) {
        return productPropertyMapper.selectById(id);
    }

    @Override
    public List<ProductPropertyDO> getPropertyList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return new ArrayList<>();
        }
        return productPropertyMapper.selectByIds(ids);
    }

    @Override
    public List<ProductPropertyDO> getPropertyList() {
        return productPropertyMapper.selectList();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void trySaveProperties(Long shopId, Map<String, Set<String>> data, @Nullable String supplyName) {
        log.debug("[trySaveProperties] shopId={}, keys={}, values={}", shopId, data.keySet(), data.values());
        Set<Long> shouldKeepPropertyIds = new HashSet<>();
        for (String name : data.keySet()) {
            try {
                lock.lock();
                Long id = saveProperties(shopId, data, supplyName, name);
                shouldKeepPropertyIds.add(id);
            } catch (DuplicateKeyException e) {
                log.warn("[trySaveProperties] shopId={}, name={} 规格创建失败：{}", shopId, name, e.getMessage());
            } catch (Exception e) {
                log.error("[trySaveProperties] shopId={}, name={} 规格创建失败", shopId, name, e);
            } finally {
                lock.unlock();
            }
        }
        log.debug("[trySaveProperties] shouldKeepPropertyIds={}", shouldKeepPropertyIds);
    }

    @Transactional(rollbackFor = Exception.class)
    public Long saveProperties(Long shopId, Map<String, Set<String>> data, @Nullable String supplyName, String name) {
        ProductPropertyDO propertyDO = productPropertyMapper.selectByShopIdAndName(shopId, name);
        if (propertyDO == null) {
            propertyDO = new ProductPropertyDO();
            propertyDO.setShopId(shopId);
            propertyDO.setName(name);
            propertyDO.setStatus(CommonStatusEnum.ENABLE.getStatus());
            propertyDO.setSupplyName(supplyName);
            productPropertyMapper.insert(propertyDO);
            log.debug("[trySaveProperties] shopId={}, name={} 规格项创建成功", shopId, name);
        }
        for (String value : data.get(name)) {
            try {
                lock.lock();
                ProductPropertyValueDO propertyValueDO = productPropertyValueMapper.selectByPropertyIdAndName(propertyDO.getId(), name);
                if (propertyValueDO == null) {
                    propertyValueDO = new ProductPropertyValueDO();
                    propertyValueDO.setShopId(shopId);
                    propertyValueDO.setPropertyId(propertyDO.getId());
                    propertyValueDO.setName(value);
                    propertyValueDO.setSupplyName(supplyName);
                    productPropertyValueMapper.insert(propertyValueDO);
                    log.debug("[trySaveProperties] propertyId={}, name={} 规格值创建成功", propertyDO.getId(), name);
                }
            } finally {
                lock.unlock();
            }
        }
        return propertyDO.getId();
    }

}
